/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.spring.initializr.generator.io.template;

import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.function.Function;

import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Mustache.TemplateLoader;
import com.samskivert.mustache.Template;

import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueRetrievalException;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;

/**
 * 使用带有可配置的资源前缀的Mustache{@link TemplateRenderer}。 模板应该有对{@code mustache}的扩展。
 *
 * @author Stephane Nicoll
 */
public class MustacheTemplateRenderer implements TemplateRenderer {

	/**
	 * 提供Mustache模板服务。
	 *
	 * <p>
	 * Basic usage: <pre>{@code
	 * String source = "Hello {{arg}}!";
	 * Template tmpl = Mustache.compiler().compile(source);
	 * Map<String, Object> context = new HashMap<String, Object>();
	 * context.put("arg", "world");
	 * tmpl.execute(context);
	 * // returns "Hello world!"
	 * }</pre>
	 */
	private final Compiler mustache;

	/**
	 * 缓存key生成器
	 */
	private final Function<String, String> keyGenerator;

	/**
	 * 模板缓存
	 */
	private final Cache templateCache;

	/**
	 * Create a new instance with the resource prefix and the {@link Cache} to use.
	 * @param resourcePrefix 用于根据模板名称定位模板的资源前缀
	 * @param templateCache 用于编译模板的缓存(可以是{@code null})不使用缓存
	 */
	public MustacheTemplateRenderer(String resourcePrefix, Cache templateCache) {
		String prefix = (resourcePrefix.endsWith("/") ? resourcePrefix : resourcePrefix + "/");
		this.mustache = Mustache.compiler().withLoader(mustacheTemplateLoader(prefix)).escapeHTML(false);
		this.keyGenerator = (name) -> String.format("%s%s", prefix, name);
		this.templateCache = templateCache;
	}

	/**
	 * Create a new instance with the resource prefix to use.
	 * @param resourcePrefix the resource prefix to apply to locate a template based on
	 * its name
	 * @see #MustacheTemplateRenderer(String, Cache)
	 */
	public MustacheTemplateRenderer(String resourcePrefix) {
		this(resourcePrefix, null);
	}

	private static TemplateLoader mustacheTemplateLoader(String prefix) {
		ResourceLoader resourceLoader = new DefaultResourceLoader();
		return (name) -> {
			String location = prefix + name + ".mustache";
			return new InputStreamReader(resourceLoader.getResource(location).getInputStream(), StandardCharsets.UTF_8);
		};
	}

	@Override
	public String render(String templateName, Map<String, ?> model) {
		Template template = getTemplate(templateName);
		return template.execute(model);
	}

	private Template getTemplate(String name) {
		try {
			if (this.templateCache != null) {
				try {
					return this.templateCache.get(this.keyGenerator.apply(name), () -> loadTemplate(name));
				}
				catch (ValueRetrievalException ex) {
					throw ex.getCause();
				}
			}
			return loadTemplate(name);
		}
		catch (Throwable ex) {
			throw new IllegalStateException("Cannot load template " + name, ex);
		}
	}

	private Template loadTemplate(String name) throws Exception {
		Reader template = this.mustache.loader.getTemplate(name);
		return this.mustache.compile(template);
	}

}
